﻿#region Using
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion Using

namespace LloydGame
{
    public class GaussianBlurImageProcessor : ImageProcessor
    {
        #region Class Fields
        protected Vector2[] m_sampleOffsetsH = new Vector2[5];
        protected float[] m_sampleWeightsH = new float[5];

        protected Vector2[] m_sampleOffsetsV = new Vector2[5];
        protected float[] m_sampleWeightsV = new float[5];

        protected RenderTarget2D m_blurTargetH;
        protected RenderTarget2D m_blurTargetV;

        protected float m_blurAmount;
        #endregion Class Fields

        #region Properties
        public float blurAmount
        {
            get
            {
                return m_blurAmount;
            }
            set
            {
                m_blurAmount = value;

                setBlurParameters(1.0f / m_imageWidth, 0, m_sampleOffsetsH, m_sampleWeightsH);
                setBlurParameters(0, 1.0f / m_imageHeight, m_sampleOffsetsV, m_sampleWeightsV);
            }
        }
        #endregion Properties

        // constructor
        public GaussianBlurImageProcessor(Game p_game, int p_imageWidth, int p_imageHeight, SurfaceFormat p_format)
            : base(p_game, p_imageWidth, p_imageHeight)
        {
            m_blurTargetH = new RenderTarget2D(
                p_game.GraphicsDevice,
                p_imageWidth,
                p_imageHeight,
                1,
                p_format);

            m_blurTargetV = new RenderTarget2D(
                p_game.GraphicsDevice,
                p_imageWidth,
                p_imageHeight,
                1,
                p_format);

            m_depthStencil = new DepthStencilBuffer(
                m_blurTargetH.GraphicsDevice,
                m_blurTargetH.Width,
                m_blurTargetH.Height,
                m_blurTargetH.GraphicsDevice.DepthStencilBuffer.Format,
                m_blurTargetH.MultiSampleType,
                m_blurTargetH.MultiSampleQuality);

            m_effect = Game.Content.Load<Effect>("Effects\\GaussianBlur");

            blurAmount = 2.0f;
        }

        protected void setBlurParameters(float p_dx, float p_dy, Vector2[] p_sampleOffsets, float[] p_sampleWeights)
        {
            p_sampleWeights[0] = ComputeGaussian(0);
            p_sampleOffsets[0] = new Vector2(0);

            float totalWeights = p_sampleWeights[0];

            // add pairs of sample taps, positioned along a line in both directions from the centre
            for (int i = 0; i < 5 / 2; i++)
            {
                float weight = ComputeGaussian(i + 1);

                // store sample weights for the positive and negative taps
                p_sampleWeights[i * 2 + 1] = weight;
                p_sampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // take advantage of the texture samplers bilinear filtering
                // sampling in between texels means "free" blurring
                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(p_dx, p_dy) * sampleOffset;

                // store texture coordinate offsets for the positive and negative taps
                p_sampleOffsets[i * 2 + 1] = delta;
                p_sampleOffsets[i * 2 + 2] = -delta;
            }

            // normalize the sample weights
            for (int i = 0; i < p_sampleWeights.Length; i++)
            {
                p_sampleWeights[i] /= totalWeights;
            }
        }

        protected float ComputeGaussian(float p_n)
        {
            float theta = m_blurAmount + float.Epsilon;

            return (float)((1.0f / Math.Sqrt(2 * Math.PI * theta)) *
                                    Math.Exp(-(p_n * p_n) / (2 * theta * theta)));
        }

        public Texture2D process(Texture2D p_blurTexture)
        {
            m_effect.CurrentTechnique = m_effect.Techniques["Blur"];

            m_effect.Parameters["blurTex"].SetValue(p_blurTexture);

            // blur horizontaly
            Game.GraphicsDevice.SetRenderTarget(0, m_blurTargetH);

            m_effect.Parameters["sampleWeights"].SetValue(m_sampleWeightsH);
            m_effect.Parameters["sampleOffsets"].SetValue(m_sampleOffsetsH);

            processPass(0);

            // blur verticaly
            Game.GraphicsDevice.SetRenderTarget(0, m_blurTargetV);
            p_blurTexture = m_blurTargetH.GetTexture();

            m_effect.Parameters["blurTex"].SetValue(p_blurTexture);

            m_effect.Parameters["sampleWeights"].SetValue(m_sampleWeightsV);
            m_effect.Parameters["sampleOffsets"].SetValue(m_sampleOffsetsV);

            processPass(0);

            Game.GraphicsDevice.SetRenderTarget(0, null);

            return m_blurTargetV.GetTexture();
        }
    }
}
